﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Lesson5
{

    // 类
    //   具有相同特征
    //   具有相同行为
    //   一类事物的抽象
    //   类是对象的模板
    //   可以通过类创建对象
    // 类声明再namespace语句块中

    // 类的声明
    //class 类名
    //{
    //    // 成员变量-- 特征
    //    // 成员方法-- 行为
    //    // 成员属性--保护特征
    //    // 构造函数 析构函数
    //    // 索引器
    //    // 运算符重载
    //    // 静态成员
    //}
    // 类声明实例  用帕斯卡命名法
    //class Person
    //{

    //}

    // 成员变量
    //   声明再类语句块中
    //   可以是任意变量类型
    //   数量没有限制，是否赋值根据需求定
    // 枚举
    enum E_Sex
    {
        Man,
        Woman
    }
    // 结构体
    struct Posotion
    {

    }
    // 另一个类
    class Pet
    {

    }
    //class Person
    //{
    //    // 成员变量
    //    string name;
    //    public int age;
    //    public E_Sex sex;     // 枚举
    //    // 如果要在类中声明一个和自己相同类型的成员变量时，不能对它进行实例化
    //    Person wife;        // 类本身，结构体中是不能包含自己
    //    public Person[] friends;  // 数组
    //    public Posotion pos;    // 位置
    //    public Pet pet;        // 宠物
    //}

    // 访问修饰符
    //   public 公共的   自己和别人都能访问和使用
    //   private 私有的   只有自己能访问和使用  不写默认是private
    //   protected 保护的  自己和子类能访问和使用

    // 成员方法  用来表示类的行为
    //   声明在类语句块中
    //   规则和函数一样
    //   受到访问修饰符的限制
    class Person
    {
        // 成员变量
        string name;
        public int age;
        public E_Sex sex;
        Person wife;
        public Person[] friends;
        public Posotion pos;
        public Pet pet;

        public string nickName;

        // 成员方法
        public void Eat()
        {
            Console.WriteLine("吃饭");
        }

        /// <summary>
        /// 说话
        /// </summary>
        /// <param name="str">说的内容</param>
        public void Speack(string str)
        {
            Console.WriteLine("说话", str);
        }

        /// <summary>
        /// 是否成年
        /// </summary>
        /// <returns></returns>
        //bool IsAdult()
        public bool IsAdult()
        {
            return age >= 18;
        }

        // 增加朋友
        public void AddFriend(Person p)
        {
            // 判断是否有朋友, 有没有实例化
            if (friends == null)
            {
                friends = new Person[] { p };
            }
            else
            {
                // 判断是否有空位
                for (int i = 0; i < friends.Length; i++)
                {
                    if (friends[i] == null)
                    {
                        friends[i] = p;
                        return;
                    }
                }
                // 没有空位，扩容
                Person[] newFriends = new Person[friends.Length + 1];
                for (int i = 0; i < friends.Length; i++)
                {
                    newFriends[i] = friends[i];
                }
                newFriends[newFriends.Length - 1] = p;
                friends = newFriends;
            }
        }
    }

    // 构造函数和析构函数
    //   构造函数
    //     在实例化对象时，用来初始化对象的函数
    //     如果不写，默认存在无参构造函数
    //   写法
    //     构造函数的名称和类名相同
    //     没有返回值
    //     没有特殊需求，一般是public的
    class Human
    {
        string name;
        int age;

        // 类中允许自己声明无参构造函数  结构体中不允许
        public Human()
        {
            name = "李伟";
            age = 18;
            Console.WriteLine("构造函数");
        }
        // 构造函数重载
        public Human(string name)
        {
            this.name = name;
        }
        public Human(int age)
        {
            this.age = age;
        }
        //public Human(string name, int age)
        //{
        //    // this代表当前调用该函数自己的对象
        //    this.name = name;
        //    this.age = age;
        //}

        // 如果实现了有参构造函数，系统就不会提供无参构造函数，需要自己再去写一个无参构造函数，否则实例化时就必须要传参数了

        // 复用代码   :this()这样的快捷方式写法，当调用两个参数的构造函数时，会先调用无参构造函数
        //public Human(string name, int age):this()
        //{
        //    Console.WriteLine("两个参数的构造函数");
        //}
        public Human(string name, int age) : this(name)     // 这个会先调用一个参数的构造函数
        {
            Console.WriteLine("两个参数的构造函数");
        }

        //public Human() : this("老李")     // this() 中的参数可以传常量进去，一般用在无参构造函数中，这样是先调用有一个参数的构造函数
        //{
        //    Console.WriteLine("无参的构造函数");
        //}

        // 析构函数  是当垃圾被真正回收的时候，才会调用
        ~Human()
        {

        }

    }

    // 析构函数
    //   在对象销毁时，用来释放资源的函数
    //   当引用类型的堆内存被回收时，会调用该函数
    //   对于需要手动管理内存的语言（如c++）,需要在析构函数中做一些内存回收的处理
    //   c#中存在自动垃圾回收机制GC
    //   所以几乎用不到析构函数，除非要在一个对象被垃圾回收时，做一些特殊处理
    // 基本语法
    //   ~类名()
    //   {
    //       // 释放资源
    //   }

    // 垃圾回收机制 GC
    //  垃圾回收的过程是，在遍历堆（Heap）上动态分配的所有对象
    //  找出那些不再被任何引用、变量引用的对象，哪些对象仍要被使用
    //  垃圾需要回收释放

    // 垃圾回收有很多算法
    //  引用计数
    //  标记清除
    //  标记整理
    //  复制集合

    // GC只负责堆(Heap)内存的垃圾回收
    // 引用类型都是存在堆（heap）中的，所以它的分配和释放都是通过垃圾回收垃圾回到机制来管理
    // 栈（Stack）上的内存是由系统自动管理的
    // 值类型都是存在栈（stack）中的，都有自己的生命周期，不用对它们进行管理，会自动分配和释放

    /*
     c#中内存回收机制的大概原理
        0代内存           1代内存           2代内存
        0代满时，进行垃圾回收，会清除未被引用的对象，引用的会迁移到1代，1代满时会进行垃圾回收（0代和1代都会进行，0代的会再次搬移到1代），1代会向2代迁移
        代的概念
            代是垃圾回收机制使用的一种算法（分代算法）
            新分配的对象都会被配置在第0代内存中
            每次分配都可能会进行垃圾回收以释放内存（0代内存满时）
        在一次内存回收过程开始时，垃圾回收器会认为堆中的全是垃圾，会进行以下两步
          1、标记对象 从根（静态字段、方法参数）开始检查引用对象，标记后为可达对象，未标记为不可达对象
             不可达对象就认为是垃圾
          2、搬迁对象压缩堆（挂起 执行托管代码线程）释放未标记的对象，搬迁可达对象，修改引用地址 搬迁到1代内存中

        大对象总被认为是第二代内存，目的是减少性能损耗，提高性能
        不会对大对象进行搬迁压缩  85000字节（83kb）以上的对象为大对象
     */



    // 成员属性
    //  用于保护成员变量
    //  为成员属性的获取和赋值添加逻辑处理
    //  属性可以让成员变量在外部 只能获取，不能修改 | 只能修改，不能获取

    // 成员属性的基本语法
    // 访问修饰符 属性类型 属性名
    //{
    //    get{}
    //    set{}
    //}
    class Student
    {
        private string name;
        private int age;
        private bool sex;

        // 属性命名  一般使用帕斯卡命名法
        public string Name
        {
            get
            {
                // ... 返回之前添加一些逻辑
                // 这个属性可以获取的内容
                return name;
            }
            set
            {
                // ... 在设置之前添加一些逻辑
                // value 关键字，表示外部传入的值，只在set属性方法中 
                name = value;
            }
        }

        // get和set前面可以加访问修饰符
        // 默认不加 会使用属性声明时的访问权限
        // 加的访问修饰符要低于属性的访问权限
        // 不能让get和set的访问权限都低于属性的权限

        public int Age
        {
            // 默认不加，会使用public
            get
            {
                return age;
            }
            set
            {
                age = value;
            }

            // 只能写不能读
            //private get  // 改成私有的
            //{
            //    return age;
            //}
            //set
            //{
            //    age = value;
            //}

            // 只能读不能写
            //get  
            //{
            //    return age;
            //}
            //private set      // 改成私有的
            //{
            //    age = value;
            //}

            // 两个都加会报错
            //private get  
            //{
            //    return age;
            //}
            //private set      // 改成私有的
            //{
            //    age = value;
            //}
        }

        // get和set可以只有一个
        // 只有一个时，没必要在前面加访问修饰符
        public bool Sex
        {
            // 一般都是一个get，能读不能改
            get
            {
                return sex;
            }
        }

        // 自动属性
        //  作用：外部不能改的特征
        //  如果类中有一个特征只希望外部能得不能改，也没有什么特殊处理，就可以直接使用自动属性
        private float height;
        public float Height
        {
            // 直接写 get  set 就是自动属性
            get;
            private set;
        }

    }

    // 索引器
    //  概念：让对象可以像数组一样通过索引访问其中元素，使程序看起来更直观
    //  语法
    //访问修饰符 返回值 this[参数类型 参数名, 参数类型 参数名, ...]
    //{
    //      内部的写法和规则与属性相同
    //}
    class Teacher
    {
        private string name;
        private int age;
        private Teacher[] mates;

        private int[,] arr;

        // 跟函数类似，但里面跟属性类似
        public Teacher this[int index]        // this[arg]可以传参数的
        {
            get
            {
                // 索引器中可以写逻辑
                return mates[index];
            }
            set
            {
                if (mates == null)
                {
                    mates = new Teacher[index];
                }
                else
                {
                    mates[index] = value;
                }
            }
        }
        // 索引器可以重载   函数名相同  参数类型、数量、顺序不同
        public int this[int i, int j]     
        {
            get
            {
                // 索引器中可以写逻辑
                return arr[i, j];
            }
            set
            {
                if (arr == null)
                {
                    arr = new int[i, j];
                }
                else
                {
                    arr[i, j] = value;
                }
            }
        }
        public string this[string name]
        {
            get
            {
                switch (name)
                {
                    case "name":
                        return name;
                }
                return "";
            }
        }
    }

    // 索引器主要作用是让我们以中括号的形式范围自定义类中的元素  规则自己定  访问时和数组一样
    // 比较适用于 在类中有数组变量时使用 可以方便的访问和进行逻辑处理
    // 注意：结构体里面也是支持索引器

    internal class Program
    {
        static void Main(string[] args)
        {
            // 面向对象
            #region 面向对象
            // 概念
            // 用程序抽象现实世界的事物
            // 类和对象
            //   关键字 class
            // 面向对象的三大特性
            //   封装 + 继承 + 多态
            // 面向对象的七大设计原则
            //   单一职责原则、开闭原则、里氏替换原则、依赖倒置原则、接口隔离原则、迪米特法则、合成复用原则

            // 对象
            //  类创建对象的过程 一般称为实例化对象
            //  类对象 都是引用类型
            //  枚举、结构体是值类型

            // 实例化对象的基本语法
            //  类名 变量名;
            //  类名 变量名 = null;
            //  类名 变量名 = new 类名();
            //  类名 变量名 = new 类名(参数列表);

            // 实例化对象
            //Person p;      // 不写就是null
            //Person p1 = null;  // null代表空，不分配堆内存空间
            //Person p2 = new Person(); // 实例化一个对象
            //Person p3 = new Person(); // 实例化一个新的对象


            // 成员变量的使用
            Person p = new Person();
            //Console.WriteLine(p.name);  // 访问不到，是因为没有设置访问修饰符，成员变量默认是private 私有的，外部无法访问
            Console.WriteLine(p.sex);

            // 成员变量的默认值、初始值
            //   值类型中数字类型默认都是0，bool默认是false
            //   引用类型默认是null
            // 如何看默认值:使用default关键字,主要是看值类型
            Console.WriteLine(default(int));  // 0

            // 成员变量的赋值
            p.age = 18;         // public的都可以赋值

            // 成员方法的使用  必须实例化对象，通过对象才能调用，相当于该对象执行了某个行为
            Person p4 = new Person();
            p4.Speack("你好");

            Person frendWang = new Person();
            frendWang.nickName = "王五";
            p4.AddFriend(frendWang);
            for (int i = 0; i < p4.friends.Length; i++)
            {
                Console.WriteLine(p4.friends[i].nickName);  // 打印出所有朋友的名字
            }

            Human h = new Human("nd", 10);  // 这里可以使用断点调试，看构造函数的执行顺序

            // 手动触发垃圾回收的方法   一般不会频繁使用，都是在Loading过场景时才调用
            // GC.Collect();

            // 成员属性的使用
            Student s = new Student();
            s.Name = "小明";              // 会执行set语句块
            Console.WriteLine(s.Name);    // 会执行get语句块

            // 索引器的使用
            Teacher t = new Teacher();
            t[0] = new Teacher();        // 索引类里加了索引器，这里就可以用标本索引访问

            // 多参数缩索引器
            t[0, 0] = 10;

            #endregion
        }
    }
}
